﻿using System;
using System.IO;
using System.Threading;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Files;

namespace Syncer
{
	public class DirectorySyncOperation : AsyncOperation<object>
	{
		#region fields
		private object _progressLock = new object();
		#endregion

		#region properties

		private DirectoryInfo SourceDirectory
		{ get; set; }
		private DirectoryInfo DestinationDirectory
		{ get; set; }
		private FileSyncScopeFilter SyncFilter
		{ get; set; }
		private FileSyncOptions SyncOptions
		{ get; set; }
		private ProgressUpdateEventArgs CurrentProgress
		{ get; set; }

		#endregion

		#region events

		public event EventHandler<ProgressUpdateEventArgs> ProgressUpdate = delegate { };

		#endregion

		#region construction

		public DirectorySyncOperation(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory)
			: this(sourceDirectory, destinationDirectory, null)
		{ }

		public DirectorySyncOperation(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory,
			FileSyncScopeFilter syncFilter)
			: this(sourceDirectory, destinationDirectory, syncFilter, FileSyncOptions.None)
		{ }

		public DirectorySyncOperation(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory,
			FileSyncScopeFilter syncFilter, FileSyncOptions syncOptions)
			: this(sourceDirectory, destinationDirectory, syncFilter, syncOptions, null)
		{ }

		public DirectorySyncOperation(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory,
			FileSyncScopeFilter syncFilter, FileSyncOptions syncOptions, AsyncCallback asyncCallback)
			: base(asyncCallback)
		{
			SourceDirectory = sourceDirectory;
			DestinationDirectory = destinationDirectory;
			SyncOptions = syncOptions;
			SyncFilter = syncFilter;

			CurrentProgress = new ProgressUpdateEventArgs(0, string.Empty, 0);
		}


		#endregion

		#region AsyncOperation
		protected override object OperationWrapper()
		{
			PerformSync();
			return null;
		}
		#endregion

		private void PerformSync()
		{
			using (var sourceProvider = new FileSyncProvider(SourceDirectory.FullName, SyncFilter, SyncOptions))
			using (var destinationProvider = new FileSyncProvider(DestinationDirectory.FullName, SyncFilter, SyncOptions))
			{
				SyncOrchestrator agent = new SyncOrchestrator();
				agent.LocalProvider = sourceProvider;
				agent.RemoteProvider = destinationProvider;
				agent.Direction = SyncDirectionOrder.Upload;

				destinationProvider.CopyingFile += new EventHandler<CopyingFileEventArgs>(destinationProvider_CopyingFile);
				destinationProvider.ApplyingChange += new EventHandler<ApplyingChangeEventArgs>(destinationProvider_ApplyingChange);
				destinationProvider.AppliedChange += new EventHandler<AppliedChangeEventArgs>(destinationProvider_AppliedChange);
				agent.SessionProgress += new EventHandler<SyncStagedProgressEventArgs>(agent_SessionProgress);

				sourceProvider.DetectingChanges += new EventHandler<DetectingChangesEventArgs>(sourceProvider_DetectingChanges);
				destinationProvider.DetectingChanges += sourceProvider_DetectingChanges;

				sourceProvider.DetectChanges();
				destinationProvider.DetectChanges();

				SendProgressNotification(new ProgressUpdateEventArgs(0, string.Empty, 0));
				agent.Synchronize();
			}
		}

		#region private helpers

		private void sourceProvider_DetectingChanges(object sender, DetectingChangesEventArgs e)
		{
			var currentMessage = string.Format("detecting changes - [{0}]", e.DirectoryPath);
			var updatedProgress = new ProgressUpdateEventArgs(0, currentMessage, 0);
			SendProgressNotification(updatedProgress);
		}

		private void destinationProvider_AppliedChange(object sender, AppliedChangeEventArgs e)
		{
			var updatedProgress = new ProgressUpdateEventArgs(CurrentProgress.TotalPercentageComplete,
				string.Format("{0} - {1}", e.ChangeType, e.NewFilePath), 100);
			SendProgressNotification(updatedProgress);
		}

		private void destinationProvider_ApplyingChange(object sender, ApplyingChangeEventArgs e)
		{
			var desc = GetCommandDescription(e.ChangeType, e.CurrentFileData, e.NewFileData);
			var updatedProgress = new ProgressUpdateEventArgs(CurrentProgress.TotalPercentageComplete,
				desc, 0);
			SendProgressNotification(updatedProgress);
		}

		private void destinationProvider_CopyingFile(object sender, CopyingFileEventArgs e)
		{
			var updatedProgress = new ProgressUpdateEventArgs(CurrentProgress.TotalPercentageComplete, CurrentProgress.CurrentOperation, e.PercentCopied);
			SendProgressNotification(updatedProgress);
		}

		private void agent_SessionProgress(object sender, SyncStagedProgressEventArgs e)
		{
			if (e.TotalWork != 0)
			{
				decimal percentageComplete = ((decimal)e.CompletedWork / (decimal)e.TotalWork)*100;
				var updatedProgress = new ProgressUpdateEventArgs(percentageComplete, CurrentProgress.CurrentOperation, CurrentProgress.CurrentOperationProgress);
				SendProgressNotification(updatedProgress);
			}
		}

		private string GetCommandDescription(ChangeType changeType, FileData currentFileData, FileData newFileData)
		{
			string desc = string.Empty;

			switch (changeType)
			{
				case ChangeType.Create:
					desc = string.Format("Creating File [{0}]", newFileData.Name);
					break;
				case ChangeType.Delete:
					desc = string.Format("Deleting File [{0}]", currentFileData.Name);
					break;
				case ChangeType.Rename:
					desc = string.Format("Renaming File [{0}] to [{1}]", currentFileData.Name, newFileData.Name);
					break;
				case ChangeType.Update:
					desc = string.Format("Updating File [{0}]", currentFileData.Name);
					break;
			}

			return desc;
		}

		#endregion

		protected void SendProgressNotification(ProgressUpdateEventArgs updatedProgress)
		{
			lock (_progressLock)
			{
				CurrentProgress = updatedProgress;
				ProgressUpdate(this, updatedProgress);
			}
		}
	}
}
